/**
 * The contents of this file are subject to the license and copyright detailed in the LICENSE and NOTICE files at the root of the source tree and available online at
 * http://www.askosi.org/license.html
 */
package be.destin.skos.core;

import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;

import org.apache.log4j.Logger;

import be.destin.rdf.changes.Status;


/**
 * @author Christophe Dupriez
 *
 */

public class TermList extends LinkedList<Term> {

	public static final long serialVersionUID = 1;
	public static final String anyLanguage = "";
	public static final String[] dirRTL = {	"ar"/*arabic*/,
											"fa"/*farsi*/,
											"he"/*hebrew*/,
											"ps"/*pashto*/,
											"sd"/*sindhi*/,
											"tt"/*tatar*/,
											"ur"/*urdu*/,
											"yi"/*Yiddish*/ };
	public static final HashSet<String> setRTL = new HashSet<String>(Arrays.asList(dirRTL));
	public static final String ENGLISH = "en";

	/** log4j category */
	private static Logger log = Logger.getLogger(TermList.class);

	/**
	 * Set the entry for a given language (fields with unique values for a language)
	 * @param language
	 * @param aValue
	 * @param source
	 * @return the assigned term
	 */
	public Term setLing(String language, String aValue, String source) {
		if (aValue == null) return null;
		if (language != null) language = language.trim();
		else language = TermList.anyLanguage;
		for (Term anEntry : this) {
			String aLang = anEntry.getLang();
			if (aLang.equals(language)) {
				anEntry.setValue(aValue);
				anEntry.setSource(source);
				return anEntry;
			}
			else if ( language != TermList.anyLanguage && aLang.startsWith(language)) {
				anEntry.setValue(aValue);
				anEntry.setSource(source);
				return anEntry;
			}
		}
		Term anEntry = new Term(language, null, aValue, source); 
		this.add(anEntry);
		return anEntry;
	}

	public Term setLing(Term aTerm) {
		if (aTerm == null) return null;
		Term currTerm = setLing(aTerm.getLang(),aTerm.getValue(),aTerm.getSource());
		Status newStatus = aTerm.getCurrentStatus();
		//if (newStatus != null) {
		currTerm.setCurrentStatus(newStatus);
		//}
		currTerm.setCurrentFormat(aTerm.getCurrentFormat(currTerm.getCurrentFormat()));
		return currTerm;
	}

	/**
	 * add a term if it is not already completely/exactly there
	 * @param aTerm: the term to be added if necessary
	 */
	public void addNew (Term aTerm) {
		if (!this.contains(aTerm)) {
			this.add(aTerm);
		}
	}

	/**
	 * @param language to collect
	 * @return the list of all terms with requested language
	 */
	public TermList getAllLing(String language) {
		if (this.size() <= 0) return null;
		if (language == null || language.length()==0) language = anyLanguage; 
		TermList result = new TermList();
		for (Term anEntry : this) {
			if (anEntry.getLang().equals(language)) {
				result.add(anEntry);
			}
		}
		return result;
	}

	/**
	 * @param strictLanguage TODO
	 * @param supraLanguage: list of languages that are more important than the user language (latin names for plants for instance)
	 * @param language: user language
	 * @return a best fit between existing and requested language (english as a minimal common d(e)nominator)
	 */
	public String getLing(LinkedList<String> supraLanguage, String language, boolean strictLanguage) {
		Term aTerm = getLingTerm(supraLanguage,language, strictLanguage);
		if (aTerm == null) return null;
		else {
			return aTerm.applyRTL();
		}
	}

	/**
	 * @param strictLanguage TODO
	 * @param supraLanguage: list of languages that are more important than the user language (latin names for plants for instance)
	 * @param language: user language
	 * @return the Term which is a best fit between existing and requested language (english as a minimal common d(e)nominator)
	 */
	public Term getLingTerm(LinkedList<String> supraLanguage, String language, boolean strictLanguage) {
		if (this.size() <= 0) return null;
		if (language == null || language.length()==0) language = TermList.anyLanguage;
		Term ownLanguage = null;
		Term anAny = null;
		Term anEnglish = null;
		Term aTerm = null;
		int supraRank = 9999;
		Term supraTerm = null;
		for (Term anEntry : this) {
			aTerm = anEntry;
			if (aTerm == null) continue;
			String aLang = aTerm.getLang();
			if (aLang == null) {
				anAny = aTerm;
			}
			else if (aLang.equals(TermList.anyLanguage)) {
				anAny = aTerm;
			}
			else {
				if (supraLanguage != null) {
					int posSupra = supraLanguage.indexOf(aLang);
					if (posSupra >= 0 && posSupra < supraRank) {
						supraRank = posSupra;
						supraTerm = aTerm;
						continue;
					}
				}
				if (aLang.startsWith(language)) {
					ownLanguage = aTerm;
				} else if (aLang.startsWith(ENGLISH)) {
					anEnglish = aTerm;
				}
			}
		}
		if (supraTerm != null) {
			return supraTerm;
		}
//if (supraLanguage==null) log.debug("no supraLanguage for "+aValue);
//else log.debug(supraLanguage+": not kept for "+aValue);
		if (ownLanguage != null) return ownLanguage;
		if (anAny != null) return anAny;
		if (anEnglish != null) return anEnglish; // Domination culturelle consentie?
		if (strictLanguage) return null;
		return aTerm;
	}

	/**
	 * Set main fields of a Term (fields with NOT unique values for a language)
	 * @param language of the term
	 * @param aValue: text for the term
	 * @param source: new source to put for the term (a given term language/text combination can have only one source)
	 * @return the added/updated Term
	 */
	public Term setLingDifferent(String language, String aValue, String source) {
		if (aValue == null) return null;
		if (language != null) language = language.trim();
		else language = TermList.anyLanguage;
		for (Term anEntry : this) {
			String aLang = anEntry.getLang();
			String aText = anEntry.getValue();
			if (aText == null) continue;
			if (aLang.equals(language) && aText.equals(aValue)) {
				anEntry.setSource(source);
				return anEntry;
			}
		}
		Term anEntry = new Term(language, null, aValue, source); 
		this.add(anEntry);
		return anEntry;
	}

	/**
	 * Add a Term (fields with NOT unique values for a language)
	 */
	public Term setLingDifferent(Term aTerm) {
		if (aTerm == null) return null;
		Term currTerm = setLingDifferent(aTerm.getLang(),aTerm.getValue(),aTerm.getSource());
		Status newStatus = aTerm.getCurrentStatus();
		//if (newStatus != null) {
		currTerm.setCurrentStatus(newStatus);
		//}
		currTerm.setCurrentFormat(aTerm.getCurrentFormat(currTerm.getCurrentFormat()));
		return currTerm;
	}

	public String toHtml(String template) {
		StringBuffer out = new StringBuffer();
		for (Term aTerm:this) {
			out.append(aTerm.toHtml(template));
		}
		return out.toString();
	}
	
	public String toRdfLangValue(String property) {
		StringBuffer out = new StringBuffer();
		for (Term aTerm:this) {
			out.append(aTerm.toRdfLangValue(property));
		}
		return out.toString();
	}
	
	public static boolean isRTL(String lang) {
		if (lang == null || lang.length() < 2) return false;
		return setRTL.contains(lang.substring(0,2));
	}
	
	public String flatten() {
		StringBuffer out = new StringBuffer();
		for (Term aTerm:this) {
			out.append(aTerm.applyRTL()+aTerm.stringSource());
			out.append("; ");
		}
		return out.toString();
	}
	
	
	/**
	 * Merges two list of Terms for fields with only one term allowed for a given language
	 * The merged-in list (the one in parameter) is "stronger" than the current one.
	 * @param source: merged-in list of Terms
	 */
	public void mergeUnique (TermList source) {
		if (source != null) {
			for (Term aTerm : source) {
				Term currTerm = setLing(aTerm.getLang(),aTerm.getValue(),aTerm.getSource());
				Status newStatus = aTerm.getCurrentStatus();
				//if (newStatus != null) {
				currTerm.setCurrentStatus(newStatus);
				//}
				currTerm.setCurrentFormat(aTerm.getCurrentFormat(currTerm.getCurrentFormat()));
			}
		}
	}

	/**
	 * Merges two list of Terms for fields with multiple terms allowed for a given language
	 * The merged-in list (the one in parameter) is "stronger" than the current one.
	 * @param source: merged-in list of Terms
	 */
	public void mergeDifferents (TermList source) {
		if (source != null) {
			for (Term aTerm : source) {
				Term currTerm = setLingDifferent(aTerm.getLang(),aTerm.getValue(),aTerm.getSource());
				Status newStatus = aTerm.getCurrentStatus();
				//if (newStatus != null) {
				currTerm.setCurrentStatus(newStatus);
				//}
				currTerm.setCurrentFormat(aTerm.getCurrentFormat(currTerm.getCurrentFormat()));
			}
		}
	}
}
